package com.clear.honghefinance.biz.modules.system.member.service.impl;

import cn.hutool.captcha.CaptchaUtil;
import cn.hutool.captcha.LineCaptcha;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
import com.clear.honghefinance.biz.constant.RedisKeyConstant;
import com.clear.honghefinance.biz.modules.system.member.domain.entity.Member;
import com.clear.honghefinance.biz.modules.system.member.domain.entity.MemberBindPhone;
import com.clear.honghefinance.biz.modules.system.member.domain.vo.AdminDTO;
import com.clear.honghefinance.biz.modules.system.member.domain.dto.GetBase64CodeDTO;
import com.clear.honghefinance.biz.modules.system.member.domain.dto.GetSmsCodeDTO;
import com.clear.honghefinance.biz.modules.system.member.domain.vo.SmsCodeResult;
import com.clear.honghefinance.biz.modules.system.member.domain.dto.PhonePasswordLoginDTO;
import com.clear.honghefinance.biz.modules.system.member.domain.dto.PhoneSmsCodeLoginDTO;
import com.clear.honghefinance.biz.modules.system.member.enums.SmsCodeTypeEnum;
import com.clear.honghefinance.biz.modules.system.member.service.IMemberService;
import com.clear.honghefinance.biz.modules.system.member.service.MemberBindPhoneService;
import com.clear.honghefinance.biz.modules.system.member.service.MemberLoginService;
import com.clear.honghefinance.common.dto.vo.TokenResponse;
import com.clear.honghefinance.common.enums.ApiResponseCodeEnum;
import com.clear.honghefinance.common.exception.BizException;
import com.clear.honghefinance.common.exception.ParameterException;
import com.clear.honghefinance.common.service.TokenService;
import com.clear.honghefinance.common.util.DateUtil;
import com.clear.honghefinance.common.util.redis.RedisUtil;
import com.google.gson.Gson;
import com.google.gson.reflect.TypeToken;
import lombok.extern.slf4j.Slf4j;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.stereotype.Service;

import javax.annotation.Resource;
import java.time.Duration;
import java.util.Objects;
import java.util.Set;
import java.util.UUID;
import java.util.concurrent.TimeUnit;

@Service
@Slf4j
public class MemberLoginServiceImpl implements MemberLoginService {

    @Resource
    private RedisUtil redisUtil;

    @Resource
    private MemberBindPhoneService memberBindPhoneService;

    @Resource
    private IMemberService memberService;

    @Resource
    private PasswordEncoder passwordEncoder;

    // 注意:如果类上加了@ConditionalOnProperty注解,需要确保类被注入容器,否则@Resource失效
    /**
     * Parameter 0 of constructor in com.clear.honghefinance.admin.controller.LoginController required a bean of type 'com.clear.honghefinance.common.service.TokenService' that could not be found.
     */
    @Resource
    private TokenService<AdminDTO> adminTokenService;

    /**
     * 获取客户端id
     *
     * @return 返回客户端id(随机生成的uuid)
     */
    @Override
    public String getClientId() {
        return UUID.randomUUID().toString().replace("-", "");
    }

    /**
     * 获取图形验证码
     *
     * @param getBase64CodeDTO req
     * @return base64图形验证码
     */
    @Override
    public String getBase64Code(GetBase64CodeDTO getBase64CodeDTO) {
        /*
         * width: 图片宽度
         * height: 图片高度
         * codeCount: 验证码个数
         * lineCount: 干扰线个数
         */
        LineCaptcha lineCaptcha = CaptchaUtil.createLineCaptcha(300, 192, 5, 1000);
        // 取出验证码
        String code = lineCaptcha.getCode();
        log.info("图形验证码{}", code);

        // 将验证码存入redis
        String key = redisUtil.buildKey(RedisKeyConstant.GRAPHIC_VERIFICATION_CODE, getBase64CodeDTO.getClientId());
        redisUtil.del(key);  // 清除旧的验证码
        redisUtil.setNx(key, code, 15L, TimeUnit.MINUTES);

        return lineCaptcha.getImageBase64();
    }

    /**
     * 获取短信验证码
     *
     * @param dto req
     */
    @Override
    public void sendSmsCode(GetSmsCodeDTO dto) {
        checkBase64Code(dto.getClientId(), dto.getCode());

        String key = redisUtil.buildKey(RedisKeyConstant.SMS_CODE, dto.getSmsCodeType(), dto.getPhone());
        // 获取缓存key，并使用gson反序列化为SmsCodeResult
        SmsCodeResult smsCodeResult = new Gson().fromJson(redisUtil.get(key), SmsCodeResult.class);
        if (Objects.nonNull(smsCodeResult)) {
            Duration duration = DateUtil.getDuration(smsCodeResult.getGetTime(), DateUtil.getSystemTime());
            if (duration.getSeconds() < 60) {
                throw new BizException("短信验证码已发送，请稍后再试");
            }
        }
        MemberBindPhone memberBindPhone = memberBindPhoneService.getMemberByPhone(dto.getPhone());
        // 注册
        if (dto.getSmsCodeType().equals(SmsCodeTypeEnum.REGISTER.getCode()) && Objects.nonNull(memberBindPhone)) {
            throw new ParameterException("phone", "该手机号已注册！");
        }
        // 登录
        if (dto.getSmsCodeType().equals(SmsCodeTypeEnum.LOGIN.getCode()) && Objects.isNull(memberBindPhone)) {
            throw new ParameterException("phone", "该手机号未注册！");
        }
        int smsCode = this.getRandom(6);
        smsCodeResult = new SmsCodeResult();
        smsCodeResult.setCode(String.valueOf(smsCode));
        smsCodeResult.setGetTime(DateUtil.getSystemTime());

        redisUtil.setNx(key, new Gson().toJson(smsCodeResult), 15L, TimeUnit.MINUTES);
        log.info("客户端id{},手机号：{},短信验证码：{}", dto.getClientId(), dto.getPhone(), smsCode);
        // 调用阿里云SMS发送验证码
        // todo 调用短信接口

    }

    /**
     * 校验短信验证码
     *
     * @param phone       phone
     * @param smsCode     短信验证码
     * @param smsCodeType 验证码类型
     * @return 成功true、失败false
     */
    @Override
    public boolean checkSmsCode(String phone, String smsCode, String smsCodeType) {
        String key = redisUtil.buildKey(RedisKeyConstant.SMS_CODE, smsCodeType, phone);
        String cacheSmsCode = redisUtil.get(key);
        // 短信验证码只用一次：无论校验成功与否，都删除
        redisUtil.del(key);
        if (Objects.equals(cacheSmsCode, smsCode)) {
            throw new ParameterException("smsCode", "短信证码错误，请重新获取验证码！");
        }
        return true;
    }

    /**
     * 校验图形验证码
     *
     * @param clientId clientId
     * @param code     code
     * @return 成功true、失败false
     */
    @Override
    public boolean checkBase64Code(String clientId, String code) {
        String cacheCode = redisUtil.get(redisUtil.buildKey(RedisKeyConstant.GRAPHIC_VERIFICATION_CODE, clientId));
        // 图形验证码只用一次：无论校验成功与否，都删除
        redisUtil.del(redisUtil.buildKey(RedisKeyConstant.GRAPHIC_VERIFICATION_CODE, clientId));
        if (!code.equalsIgnoreCase(cacheCode)) {
            throw new ParameterException("code", "图形验证码错误");
        }
        return true;
    }

    /**
     * 手机号密码登录
     *
     * @param dto req
     * @return 用户token信息
     */
    @Override
    public TokenResponse phonePasswordLogin(PhonePasswordLoginDTO dto) {
        // 校验图形验证码
        this.checkBase64Code(dto.getClientId(), dto.getCode());
        // 判断手机号是否已注册
        MemberBindPhone memberBindPhone = memberBindPhoneService.getMemberByPhone(dto.getPhone());
        if (Objects.isNull(memberBindPhone)) {
            throw new BizException(ApiResponseCodeEnum.ACCOUNT_PASSWORD_ERROR.getCode(), "该手机号未注册");
        }
        // 校验密码是否匹配: 传入密码与数据库密码(加密后)进行比对
        if (!passwordEncoder.matches(dto.getPassword(), memberBindPhone.getPassword())) {
            throw new BizException(ApiResponseCodeEnum.ACCOUNT_PASSWORD_ERROR.getCode(),
                    ApiResponseCodeEnum.ACCOUNT_PASSWORD_ERROR.getMessage());
        }
        // 通过会员id查询会员信息
        LambdaQueryWrapper<Member> queryWrapper = Wrappers.<Member>lambdaQuery()
                .eq(Member::getId, memberBindPhone.getMemberId());
        Member member = memberService.getOne(queryWrapper);

        // 生成token，并将用户id，租户id，角色信息存入redis缓存中
        return loginSuccess(memberBindPhone.getMemberId(), member.getTenantId(), member.getSysRoleIds());
    }

    /**
     * 手机短信验证码登录
     *
     * @param dto req
     * @return 用户token信息
     */
    @Override
    public TokenResponse phoneSmsCodeLogin(PhoneSmsCodeLoginDTO dto) {
        // 校验短信验证码
        this.checkSmsCode(dto.getPhone(), dto.getSmsCode(), SmsCodeTypeEnum.LOGIN.getCode());
        // 判断该手机号是否已注册
        MemberBindPhone memberBindPhone = memberBindPhoneService.getMemberByPhone(dto.getPhone());
        if (Objects.isNull(memberBindPhone)) {
            throw new ParameterException("phone", "该手机号未注册");
        }
        // 返回用户token信息
        Member member = memberService.getOne(
                Wrappers.<Member>lambdaQuery().eq(Member::getId, memberBindPhone.getMemberId())
        );
        return loginSuccess(memberBindPhone.getMemberId(), member.getTenantId(), member.getSysRoleIds());
    }

    /**
     * 获取客户端token信息
     *
     * @param clientId 客户端id
     * @return 用户token信息
     */
    @Override
    public TokenResponse getClientToken(String clientId) {
        String cacheToken = redisUtil.get(redisUtil.buildKey(RedisKeyConstant.CLIENT_TOKEN_KEY, clientId));
        if (Objects.nonNull(cacheToken)) {
            return new Gson().fromJson(cacheToken, new TypeToken<TokenResponse>() {
            }.getType());
        }
        return null;
    }

    /**
     * @param memberId   会员id\用户id
     * @param tenantId   租户id
     * @param sysRoleIds 角色ids
     * @return
     */
    private TokenResponse loginSuccess(long memberId, long tenantId, String sysRoleIds) {
        try {
            AdminDTO adminDTO = new AdminDTO();
            adminDTO.setId(memberId);
            adminDTO.setTenantId(tenantId);
            adminDTO.setSysRoleIds(new Gson().fromJson(sysRoleIds, new TypeToken<Set<Long>>() {
            }.getType()));
            // 生成token
            adminTokenService.setToken(adminDTO);

            return adminDTO.getToken();
        } catch (Exception e) {
            throw new BizException(ApiResponseCodeEnum.LOGIN_ERROR.getMessage(), e);
        }
    }

    /**
     * 随机生成num位数字
     *
     * @param length 数字的长度
     * @return
     */
    private static int getRandom(int length) {
        return (int) ((Math.random() * 9 + 1) * Math.pow(10, length - 1));
    }
}
